package org.example.leetcode.codeExecutor.utils;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.KeyPair;
import com.jcraft.jsch.Session;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.example.leetcode.common._enum.CodeStatusEnum;
import org.example.leetcode.common._enum.LanguageEnum;
import org.example.leetcode.common.domain.CodeDO;
import org.example.leetcode.common.domain.CodeResult;
import org.example.leetcode.common.exception.MyException;
import org.jetbrains.annotations.NotNull;
import org.jsoup.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ssh 连接工具类
 */
@Slf4j
public class SSHTools {

    private static final Logger logger = LoggerFactory.getLogger(SSHTools.class);
    public static final String TEST_DIR="/root/leetcode/test/";
    public static final String C_DOCKERFILE="/root/leetcode/C/Dockerfile";
    public static final String BASE_URL="/root/leetcode/";
    public static final String C_TARGET_URL="/root/leetcode/C";
    public static final String TEMP_URL="/root/leetcode/temp";
    public static final String AND=" && ";

    private Session session;

    /**
     * 获取实例
     *
     * @return 实例
     */
    public static SSHTools getInstance() {
        return new SSHTools();
    }

    public static void deleteImages(String imageN) {
        SSHTools sshTools = SSHTools.getSSHTools();
        try {
            sshTools.executeShell("docker rmi "+imageN);
        } catch (Exception e) {
            log.error("删除镜像失败{}",imageN);
        }
    }

    /**
     * 登录主机
     * <p>
     * 密码登录
     *
     * @param hostIp   主机IP
     * @param hostPort 主机ssh端口
     * @param user     主机登录用户名
     * @param password 主机登录密码
     */
    private void loginByPassword(@NotNull String hostIp,
                                 @NotNull Integer hostPort,
                                 @NotNull String user,
                                 @NotNull String password) {
        try {
            JSch jsch = new JSch();
            session = jsch.getSession(user, hostIp, hostPort);
            session.setPassword(password);
            Properties properties=new Properties();
            properties.put("StrictHostKeyChecking", "no");
            properties.put("PreferredAuthentications","password");
            // 设置第一次登陆的时候提示，可选值:(ask | yes | no)
            session.setConfig(properties);
            // 连接超时
            session.connect(1000 * 10);
        } catch (JSchException e) {
            logger.error("登录时发生错误!", e);
        }
    }

    /**
     * 登录主机
     * <p>
     * 密钥登录
     *
     * @param hostIp     主机IP
     * @param hostPort   主机ssh端口
     * @param user       主机登录用户名
     * @param privateKey 密钥
     */
    private void loginByKey(@NotNull String hostIp,
                            @NotNull Integer hostPort,
                            @NotNull String user,
                            @NotNull String privateKey) {
        try {
            JSch jsch = new JSch();
            jsch.addIdentity(user, privateKey.getBytes(), null, null);
            session = jsch.getSession(user, hostIp, hostPort);
            // 设置第一次登陆的时候提示，可选值:(ask | yes | no)
            session.setConfig("StrictHostKeyChecking", "no");
            session.setConfig("PreferredAuthentications", "publickey");
            // 连接超时
            session.connect(1000 * 10);
        } catch (JSchException e) {
            logger.error("登录时发生错误!", e);
        }
    }

    /**
     * 登录后，主机执行指定命令
     *
     * @param command 命令
     * @return 执行命令后结果
     */
    public SSHResult executeShell(@NotNull String command) throws Exception {
        logger.info("exe cmd: {}", command);

        byte[] tmp = new byte[1024];
        byte[] error=new byte[1024];
        // 命令返回的结果
        StringBuilder resultBuffer = new StringBuilder();
        StringBuilder errorBuffer = new StringBuilder();

        ChannelExec exec = (ChannelExec) session.openChannel("exec");
        exec.setCommand(command);
        exec.connect();

        // 返回结果流（命令执行错误的信息通过getErrStream获取）
        InputStream stdStream = exec.getInputStream();
        InputStream errorStream=exec.getErrStream();
        try {
            boolean a=true;
            boolean b=true;
            // 开始获得SSH命令的结果
            while (!Thread.interrupted()) {
                while (stdStream.available() > 0&&!Thread.currentThread().isInterrupted()) {
                    int i = stdStream.read(tmp, 0, 1024);
                    if (i < 0) {
                        break;
                    }
                    resultBuffer.append(new String(tmp, 0, i));
                }
//                a=false;
                while(errorStream.available()>0&&!Thread.currentThread().isInterrupted()){
                    int i=errorStream.read(error,0,1024);
                    if(i<0){
                        break;
                    }
                    errorBuffer.append(new String(error,0,i));
                }
//                b=false;
                if (exec.isClosed()) {
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (Exception e) {
                    logger.error("执行命令发生错误!", e);
                    exec.disconnect();
                }
            }
        } finally {
            if (exec.isConnected()) {
                exec.disconnect();
            }
        }
        String message = resultBuffer.toString();
        String errorMessage=errorBuffer.toString();
        logger.info("exe cmd return : {}", message);
        logger.info("exe cmd error : {}", errorMessage);
        SSHResult sshResult = new SSHResult();
        sshResult.setMessage(message);
        sshResult.setError(errorMessage);
        return sshResult;
    }

    /**
     * 关闭连接
     */
    public void close() {
        if (Objects.nonNull(session) && session.isConnected()) {
            session.disconnect();
        }
    }

    /**
     * 生成SSH RSA密钥对
     *
     * @param comment 公钥注释
     * @return L 公钥  R 私钥
     */
    public static Pair<String, String> generateKeyPair(String comment) throws JSchException {
        JSch jsch = new JSch();
        KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA);
        ByteArrayOutputStream baos;
        //私钥
        baos = new ByteArrayOutputStream();
        keyPair.writePrivateKey(baos);
        String privateKeyString = baos.toString();
        //公钥
        baos = new ByteArrayOutputStream();
        keyPair.writePublicKey(baos, comment);
        String publicKeyString = baos.toString();
        keyPair.dispose();
        return Pair.of(publicKeyString, privateKeyString);
    }
    public static SSHResult compileAndExecuteCode(SSHTools sshTools, String codeFile, String container){
        try {
            SSHResult res = sshTools.executeShell("docker run -i " + container + "< " + codeFile);
            return res;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static SSHTools getSSHTools(){
        SSHTools sshTools=SSHTools.getInstance();
//        sshTools.loginByPassword("47.101.182.243", 22, "root", "Mrzhang020823!");
        sshTools.loginByPassword("192.168.207.128", 22, "root", "mrzhang020823");
        return sshTools;
    }
    public static SSHResult     buildDockerImage(String imageName,String fileName,LanguageEnum languageEnum){
        //docker build -t c_container -f /root/Dockerfile --build-arg FILE_NAME=./a.c .
//        curl 127.0.0.1:9000/leetcode/test.c -o /root/leetcode/c/temp.c
        try {
            SSHTools sshTools = SSHTools.getSSHTools();
            String s="cd "+languageEnum.getTargetUrl()+"&&docker build " +
                    " -t " + imageName +
                    " -f " + languageEnum.getDockerFile() +
                    " --build-arg ARG=" + fileName+" .";
            SSHResult sshResult = sshTools.executeShell(s);
            sshTools.close();
            return sshResult;
        } catch (Exception e) {
            throw new MyException();
        }
    }
    public static void downloadFile(String minioUrl,String targetUrl){
        SSHTools sshTools = SSHTools.getSSHTools();
        String s="curl "+minioUrl+" -o "+targetUrl;
        try {
            sshTools.executeShell(s);
            int i=0;
            while(i<=5){
                if(SSHTools.fileIsExist(targetUrl)){
                    break;
                }
                i++;
                Thread.sleep(200);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 从minio里下载用户代码到指定路径
     * @param bucket
     * @param phone
     * @param type
     * @param filename
     */
    public static void downloadCodeFile(String bucket,String phone,String type,String filename){
        try {
            String minioUrl=" 127.0.0.1:9000/" + bucket + "/" + phone + "/" + type + "/" + filename;
            String targetUrl=" /root/leetcode/"+type+"/"+filename;
            downloadFile(minioUrl,targetUrl);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取测试用例
     * @param title
     * @return
     */
    public static String getTestFile(String title){
        boolean folderExist = MinIOUtils.isObjectExist(MinIOUtils.LEETCODE, MinIOUtils.getTestObject(title));
        if(!folderExist){
            throw new MyException("题目尚未初始化，请咨询管理员");
        }
        SSHTools sshTools = SSHTools.getSSHTools();
        String fileUrl="/root/leetcode/test/"+title+".txt";
        String s="curl 127.0.0.1:9000/leetcode/test/"+title+".txt"+" -o "+fileUrl;
        try {
            SSHResult sshResult = sshTools.executeShell(s);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return fileUrl;
    }
    public static boolean deleteFile(String url){
        if(StringUtils.isEmpty(url)){
            return true;
        }
        try {
            SSHTools sshTools = SSHTools.getSSHTools();
            sshTools.executeShell("rm -f "+url);
            return true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 执行代码容器
     * @param imageName
     * @param testFileUrl
     * @param codeResult
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static String runDocker(String imageName, String testFileUrl, CodeResult codeResult) throws ExecutionException, InterruptedException {
        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        Callable callable = new Callable() {
            @Override
            public String call() throws Exception {
                String s = "docker run -i " + imageName + "<" + testFileUrl;
                SSHTools sshTools = SSHTools.getSSHTools();
                SSHResult sshResult = sshTools.executeShell(s);
                stopWatch.stop();
                log.info("代码执行{}",stopWatch.getTotalTimeMillis());
                return sshResult.getMessage();
            }
        };
        FutureTask task=new FutureTask(callable);
        Thread thread = new Thread(task);
        thread.start();
        //todo 执行时间
        Thread.sleep(4000);
        String res=null;
        if(!thread.isAlive()){
            res= (String) task.get();
            codeResult.setExecuteTime(stopWatch.getTotalTimeMillis());
        }
        else{
            thread.interrupt();
            codeResult.setStatus(CodeStatusEnum.out_of_time.getCode());
        }
        return res;
    }
    public static void deleteUnusedContainer() {
        SSHTools sshTools=SSHTools.getSSHTools();
        try {
            sshTools.executeShell("docker rm $(docker ps -a -q)");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 初步编译
     * @param compileCode
     * @param fileUrl
     * @param type
     * @return
     */
    public static String compile(String compileCode,String fileUrl,String type){
        SSHTools sshTools = SSHTools.getSSHTools();
        try {
            String url=SSHTools.BASE_URL+type+"/"+fileUrl;
            SSHResult sshResult = sshTools.executeShell("cd "+TEMP_URL+AND+compileCode + " " + url);
            return StringUtils.isEmpty(sshResult.getError())?null:sshResult.getError().substring(sshResult.getError().indexOf("error"));
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new RuntimeException(e);
        }
    }
    public static boolean deleteContainerByImageName(String imageName){
        String s="docker rm -f $( docker ps -aq --no-trunc  --filter ancestor=";
        try {
            s+=imageName+")";
            SSHTools sshTools = SSHTools.getSSHTools();
            sshTools.executeShell(s);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }
    public static boolean deleteNoneImages(){
        SSHTools sshTools = SSHTools.getSSHTools();
        try {
            sshTools.executeShell("docker rmi $(docker images | grep \"none\" | awk '{print $3}')");
            return true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
    public static SSHResult getDockerMemory(){
        SSHTools sshTools = SSHTools.getSSHTools();
        try {
            SSHResult sshResult = sshTools.executeShell("docker stats --no-stream");
            return sshResult;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static boolean fileIsExist(String url){
        SSHTools sshTools = SSHTools.getSSHTools();
        try {
            SSHResult sshResult = sshTools.executeShell("find " + url);
            return !StringUtils.isEmpty(sshResult.getMessage());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static boolean isExecuteValid(SSHResult sshResult){
        return !StringUtils.hasText(sshResult.getError());
    }
    @Data
    public static class SSHResult{
        private String message;
        private String error;
    }

    public static void main(String[] args) throws Exception {
        SSHTools sshTools = SSHTools.getSSHTools();
        SSHResult sshResult = sshTools.executeShell("docker build -t two-sum13917034124c -f /root/leetcode/C/Dockerfile --build-arg ARG=two-sum139170341241710664669661.c .");
        sshTools.close();
        System.out.println(sshResult);
    }



}
